use anyhow::anyhow;
use anyhow::Result;
use clap::Args;
use fudata::db;
use fudata::model::todo;
use fudata::model::todo::Todo;
use fudata::model::todo::TodoType;
use log::debug;
use log::error;
use log::info;
use tokio::time::Interval;
use zenoh::queryable::Queryable;
use zenoh::sample::Sample;
use zenoh::Session;
use zenoh::SessionDeclarations;
use zenoh::prelude::r#async::AsyncResolve;

use std::time::Duration;
use tokio::time::interval;
use zenoh::key_expr::KeyExpr;

use crate::cli::cnf::zenohconf::CommonArgs;
use crate::cli::cnf::LOGO;
use crate::cli::commands::register_ctrl_c;
use crate::cli::commands::wait_until_next_scheduled_time;
use crate::todo::sync_klines_todo;
use crate::todo::sync_securities_todo;
use crate::todo::worker_query;
use crate::todo::worker_sync;
use crate::todo::MAX_WORKERS;
use tokio::select;

#[derive(Args, Debug)]
pub struct SyncCommandArguments {
    #[arg(
        help = "Database path used for storing data",
        short = 'p',
        long = "path",
        requires = "path",
        default_value = "memory"
    )]
    pub path: String,

    #[arg(
        help = "settings to craw securities",
        short = 's',
        long = "settings",
        default_value = "./config.toml"
    )]
    /// settings in toml format, e.g. config.toml
    pub settings: String,
    #[arg(short, long, default_value = "demo/example/**")]
    /// The selection of resources to store.
    key: KeyExpr<'static>,
    #[arg(long)]
    /// Declare the storage as complete w.r.t. the key expression.
    complete: bool,
    #[command(flatten)]
    common: CommonArgs,
}

///
/// https://zenoh.io/docs/overview/zenoh-in-action/
///
/// This animation shows a simple query in action through Zenoh. You can see the presence of storages and queryables.
/// A queryable is any process that can reply to queries. A storage is a combination of a subscriber and a queryable.
///
pub async fn init(SyncCommandArguments {
    path,
    settings,
    key,
    complete,
    common,
}: SyncCommandArguments) -> Result<()> {
    let ip = "127.0.0.1";
    let port: u16 = 8800; //8000;
    let dbrs = db::init2(ip, port).await;
    match dbrs {
        Ok(_) => {}
        Err(e) => {
            return Err(anyhow!(e));
        }
    }

    info!("Starting fudacraw sync...");

    debug!("(sync::init) arguments: path= {}", path);
    debug!("(sync::init) arguments: settings=  {}", settings);

    info!("Opening session...");

    let _config = zenoh::config::Config::default();
    let config: zenoh::config::Config = common.into();
    let session = zenoh::open(config).res().await.unwrap().into_arc();
    let session_clone1 = session.clone();

    // Listen for Ctrl-C signal
    let shutdown_rx_arc = register_ctrl_c();
    let query_injector = worker_query::start_worker_pool(MAX_WORKERS);
    let sync_injector = worker_sync::start_worker_pool(session_clone1, MAX_WORKERS);

    let main_state = MainState::new(&session).await;

    let mut _i: i32 = 0;
    loop {
        // Wrap the receiver in a cloneable Arc<Mutex<_>> to allow concurrent access
        let mut shutdown_rx_locked = shutdown_rx_arc.lock().await;

        select!(
                _ = shutdown_rx_locked.recv()  => {
                    info!("Shutting down from signal handler");
                    break;
                }

                _logo_time = wait_until_next_scheduled_time("0 0/30 * * * *","_logo_time" )=>{
                    println!("{}", LOGO );
                }
                
                generate_sync_todos_next_time = wait_until_next_scheduled_time("0/10 * * * * *","generate_sync_todos_next_time" )=>{
                    info!("\n generate_sync_todos scheduled time: {:?} ", generate_sync_todos_next_time );
                    let _ = generate_sync_todos().await;
                }
    
                get_sync_todos_next_time = wait_until_next_scheduled_time("0/5 * * * * *","get_sync_todos_next_time" )=>{
                    info!("\n sync_next_time scheduled time: {:?} ", get_sync_todos_next_time );
                    let rs = get_oldest_sync_todos().await;
                    match rs{
                        Ok(todos) => {
                            for todo in todos{
                                let _= sync_injector.push(todo);
                            }
                        },
                        Err(e) => error!("(sync::get_oldest_sync_todos){:?}",e),
                    }
                }

                // read local database: provide query security service to remote peers
                query_securities = main_state.queryable_securities.recv_async() => {
                    if let Ok(query) = query_securities {
                        let mut is_other_peer = true; 
                        if let Some(zid) = query.value(){if zid.to_string() == session.zid().to_string(){ is_other_peer=false }};

                        if is_other_peer{
                            // get a query request from remote peer
                            let _=  query_injector.push(query);
                        }
                    }
                },

                // read local database: provide query klines service to remote peers
                query_klines = main_state.queryable_klines.recv_async() => {
                    if let Ok(query) = query_klines {
                        let mut is_other_peer = true; 
                        if let Some(zid) = query.value(){if zid.to_string() == session.zid().to_string(){ is_other_peer=false }};

                        if is_other_peer{
                            // get a query request from remote peer
                            let _=   query_injector.push(query);
                        }
                    }
                },
            );
    }

    Ok(())
}

/// Represents the state needed for managing security synchronization.
pub struct MainState<'a> {
    pub subscriber_demo: zenoh::subscriber::Subscriber<'a, flume::Receiver<Sample>>,
    pub queryable_securities: Queryable<'a, flume::Receiver<zenoh::queryable::Query>>,
    pub queryable_klines: Queryable<'a, flume::Receiver<zenoh::queryable::Query>>,
    pub queryable_example: Queryable<'a, flume::Receiver<zenoh::queryable::Query>>,
    pub timer: Interval,
}

impl<'a> MainState<'a> {
    pub async fn new(session: &'a Session) -> Self {
        let key = "demo/example/**";
        let complete = false;
        info!("Declaring Subscriber on '{key}'...");
        let subscriber_demmo: zenoh::subscriber::Subscriber<flume::Receiver<Sample>> = session
            .declare_subscriber(key)
            .res().await
            .unwrap();

        info!("Declaring Queryable on '{key}'...");

        let queryable_securities = session
            .declare_queryable("securities")
            .complete(false)
            .res().await
            .unwrap();

        let queryable_klines = session
            .declare_queryable("klines")
            .complete(complete)
            .res().await
            .unwrap();

        let queryable_example = session
            .declare_queryable("demo/example/**")
            .complete(complete)
            .res().await
            .unwrap();

        // Create a timer that ticks every x seconds
        let timer: tokio::time::Interval = interval(Duration::from_secs(10));

        Self {
            subscriber_demo: subscriber_demmo,
            queryable_securities,
            queryable_klines,
            queryable_example,
            timer,
        }
    }
}

async fn generate_sync_todos() {
    if false {
        // sync securities
    } else {
        let _ = sync_securities_todo::generate_todo_sync_securities().await;
        // sync klines
        let _ = sync_klines_todo::generate_todos_sync_klines().await;
    }
}

async fn get_oldest_sync_todos() -> Result<Vec<Todo>> {
    let type_ = Some(vec![TodoType::SyncSecurities, TodoType::SyncKlines]); //None;
    let status = Some(todo::TodoStatus::Pending);
    // Select todos and push them onto the queue if not already enqueued
    db::todo::select(
        &type_,
        // &Some(TodoType::UpdateEtfList),
        // &Some(TodoType::UpdateEtfKlines),
        &status,
        false,
        0,
        MAX_WORKERS as u16
    ).await
}
